// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#![forbid(unsafe_op_in_unsafe_fn)]
#![forbid(unsafe_code)]

mod add;
mod gen;
mod update;
mod util;
mod vendor;

use anyhow::{Context, Result};
use clap::{arg, command, Parser, Subcommand};
use gnrt_lib::*;

#[derive(Debug, Parser)]
struct GnrtArgs {
    #[arg(long, help = "path to the cargo executable")]
    cargo_path: Option<String>,
    #[arg(long, help = "path to the rustc executable")]
    rustc_path: Option<String>,
    #[command(subcommand)]
    command: Command,
}

#[derive(Debug, Subcommand)]
enum Command {
    #[command(about = "Add a new third-party crate dependency in //third_party/rust")]
    Add(AddCommandArgs),
    #[command(about = "Generate GN build rules from third_party/rust crates")]
    Gen(GenCommandArgs),
    #[command(about = "Update the Cargo.lock to newer versions for //third_party/rust")]
    Update(UpdateCommandArgs),
    #[command(about = "Download all third-party crate dependencies in //third_party/rust")]
    Vendor(VendorCommandArgs),
}

#[derive(Debug, Parser)]
struct AddCommandArgs {
    #[arg(name = "OPTIONS", help = "Options to pass through to 'cargo add'")]
    passthrough: Vec<String>,
}

#[derive(Debug, Parser)]
struct GenCommandArgs {
    #[arg(
        long,
        name = "RUST_SRC_ROOT",
        help = " \
        Generate build files for Rust std library. RUST_SRC_ROOT (relative to \
        the root of the Chromium repo) must point to the Rust checkout to \
        generate build files for. It must have vendored dependencies. \
        Generated paths are rewritten to point into the toolchain package, as \
        if generated by package_rust.py."
    )]
    for_std: Option<String>,
    #[arg(
        long,
        help = " \
        Exit before writing BUILD.gn files, instead serialize the template \
        engine input and write it to a file (or files) named `gnrt-template-input.json`."
    )]
    dump_template_input: bool,
}

#[derive(Debug, Parser)]
struct UpdateCommandArgs {
    #[arg(name = "OPTIONS", help = "Options to pass through to 'cargo update'")]
    passthrough: Vec<String>,
}

#[derive(Debug, Parser)]
struct VendorCommandArgs {
    #[arg(
        long,
        name = "CRATE_NAME",
        num_args = 0..,
        help = "\
        Don't apply patches from the chromium_crates_io/patches directory \
        to newly vendored crates. If a crate name is given as a value for the \
        flag, patches will only not be applied for that crate."
    )]
    no_patches: Option<Vec<String>>,
    #[arg(
        long,
        help = " \
        Exit before writing README.chromium files, instead serialize the template \
        engine input and write it to files named `gnrt-template-input.json`."
    )]
    dump_template_input: bool,
}

fn main() -> Result<()> {
    let mut logger_builder = env_logger::Builder::new();
    logger_builder.write_style(env_logger::WriteStyle::Always);
    logger_builder.filter(None, log::LevelFilter::Warn);
    logger_builder.parse_default_env();
    logger_builder.format(format_log_entry);
    logger_builder.init();

    let args = GnrtArgs::parse();

    let paths = paths::ChromiumPaths::new().context("Could not find chromium checkout paths")?;
    let tools = paths::ToolPaths { cargo: args.cargo_path.clone(), rustc: args.rustc_path.clone() };

    match args.command {
        Command::Add(args) => add::add(args, &tools, &paths),
        Command::Gen(args) => gen::generate(args, &tools, &paths),
        Command::Update(args) => update::update(args, &tools, &paths),
        Command::Vendor(args) => vendor::vendor(args, &tools, &paths),
    }
}

fn format_log_entry(
    fmt: &mut env_logger::fmt::Formatter,
    record: &log::Record,
) -> std::io::Result<()> {
    use std::io::Write;

    let level = fmt.default_styled_level(record.level());
    write!(fmt, "[{level}")?;
    if let Some(f) = record.file() {
        write!(fmt, " {f}")?;
        if let Some(l) = record.line() {
            write!(fmt, ":{l}")?;
        }
    }
    writeln!(fmt, "] {msg}", msg = record.args())
}
